package org.jeecg.common.system.base.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.CheckFailedException;
import org.jeecg.common.system.base.entity.FieldRedisItem;
import org.jeecg.common.system.base.service.GenericService;
import org.jeecg.common.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 通用 ServiceImpl
 *
 * @author chechen
 * @date 2022-04-15
 */
@Slf4j
public abstract class GenericServiceImpl<M extends BaseMapper<E>, E> extends ServiceImpl<M, E> implements GenericService<E> {

    /**
     * redis 工具类
     */
    @Setter(onMethod_ = @Autowired)
    protected RedisUtil redisUtil;

    /**
     * 缓存默认时长 一个月（单位秒）
     */
    public static final long CACHE_TIME_DEFAULT = 2073600;
    protected String name = this.getName();
    protected List<FieldRedisItem<E>> allFieldCacheList = this.getAllFieldCacheList();
    protected List<FieldRedisItem<E>> fieldCacheList = this.getFieldCacheList();
    protected FieldRedisItem<E> primaryKeyCacheItem = this.getPrimaryKeyCacheItem();
    protected FieldRedisItem<E> selectAllDataCache = this.getSelectAllDataCacheItem();
    protected static final String DELETE_CACHE_MESSAGE_TEMPLATE = "获取并删除「{}」的 Redis Keys";


    private Serializable getId(E entity) {
        return this.primaryKeyCacheItem.getFieldFunction().apply(entity);
    }


    private FieldRedisItem<E> getPrimaryKeyCacheItem() {
        Optional<FieldRedisItem<E>> first = this.allFieldCacheList.stream().filter(FieldRedisItem::isPrimaryKey).findFirst();
        return first.orElse(null);
    }


    private List<FieldRedisItem<E>> getFieldCacheList() {
        return this.allFieldCacheList.stream().filter(FieldRedisItem::isField).collect(Collectors.toList());
    }


    private FieldRedisItem<E> getSelectAllDataCacheItem() {
        Optional<FieldRedisItem<E>> first = this.allFieldCacheList.stream().filter(FieldRedisItem::isSelectAllData).findFirst();
        return first.orElse(null);
    }


    /**
     * 根据id获取实体（缓存）
     *
     * @param id id
     * @return 实体信息
     */
    @Override
    public E getByIdCache(Serializable id) {
        Assert.isTrue(primaryKeyCacheItem != null, this.name + "没有设置主键 RedisKey");

        String key = String.format(CommonConstant.MIDDLE, this.primaryKeyCacheItem.getKeyPrefix(), id);
        E entity;
        if (this.redisUtil.hasKey(key)) {
            entity = this.redisUtil.getEntity(key, super.entityClass);
        } else {
            entity = super.baseMapper.selectById(id);
            if (Objects.nonNull(entity)) this.redisUtil.set(key, entity, CACHE_TIME_DEFAULT);
        }
        return entity;
    }


    /**
     * 根据实体字段获取实体列表（缓存）
     *
     * @param fieldValue 字段值
     * @param keyPrefix  redisKey 前缀
     * @return 实体列表
     */
    @Override
    public List<E> selectListByFieldCache(Serializable fieldValue, String keyPrefix) {
        Optional<FieldRedisItem<E>> first = this.allFieldCacheList.stream().filter(fieldRedisItem ->
                fieldRedisItem.getKeyPrefix().equals(keyPrefix)).findFirst();
        if (!first.isPresent())
            throw new CheckFailedException(this.name + "没有存储这种类型的 RedisKey");

        final String key = String.format(CommonConstant.MIDDLE, keyPrefix, fieldValue);
        List<E> entityList = this.redisUtil.getList(key, super.entityClass);
        if (entityList == null || entityList.isEmpty()) {
            entityList = super.baseMapper.selectList(new LambdaQueryWrapper<E>().eq(first.get().getFieldFunction(), fieldValue));
            if (!entityList.isEmpty()) this.redisUtil.set(key, entityList, CACHE_TIME_DEFAULT);
        }
        return entityList;
    }


    @Override
    public List<E> selectListByFieldCache(SFunction<E, Serializable> fieldFunction, Serializable fieldValue) {
        LambdaMeta fieldMeta = LambdaUtils.extract(fieldFunction);
        Optional<FieldRedisItem<E>> first = this.allFieldCacheList.stream().filter(fieldRedisItem -> {
            if (fieldRedisItem.getFieldFunction() == null) return false;
            LambdaMeta fieldItemMeta = LambdaUtils.extract(fieldRedisItem.getFieldFunction());
            return fieldMeta.getInstantiatedClass() == fieldItemMeta.getInstantiatedClass() &&
                    fieldMeta.getImplMethodName().equals(fieldItemMeta.getImplMethodName());
        }).findFirst();
        if (!first.isPresent())
            throw new CheckFailedException(this.name + "此字段没有存储设置相应的 RedisKey");

        final String key = String.format(CommonConstant.MIDDLE, first.get().getKeyPrefix(), fieldValue);
        List<E> entityList = this.redisUtil.getList(key, super.entityClass);
        if (entityList == null || entityList.isEmpty()) {
            entityList = super.baseMapper.selectList(new LambdaQueryWrapper<E>().eq(fieldFunction, fieldValue));
            if (!entityList.isEmpty()) this.redisUtil.set(key, entityList, CACHE_TIME_DEFAULT);
        }
        return entityList;
    }


    @Override
    public List<E> selectListAllCache() {
        Assert.isTrue(selectAllDataCache != null, this.name + "没有设置全查 RedisKey");

        String selectAllKeyPrefix = selectAllDataCache.getKeyPrefix();
        List<E> allEntityList;
        long listSize = this.redisUtil.lGetListSize(selectAllKeyPrefix);
        if (listSize > 0) {
            allEntityList = this.redisUtil.lGet(selectAllKeyPrefix, 0, listSize, this.entityClass);
        } else {
            allEntityList = super.baseMapper.selectList(null);
            if (!allEntityList.isEmpty())
                this.redisUtil.lSet(selectAllKeyPrefix, CACHE_TIME_DEFAULT, allEntityList.toArray());
        }

        return allEntityList;
    }


    /**
     * 插入实体（缓存）
     * 非主键缓存删除策略
     *
     * @param entity 待插入的实体
     * @return 受影响行数
     */
    @Override
    public int insertCache(E entity) {
        int insertRecordCount = super.baseMapper.insert(entity);

        if (insertRecordCount == 0) {
            log.error("插入「{}」失败", this.name);
            log.error(entity.toString());
            return insertRecordCount;
        }

        for (FieldRedisItem<E> fieldRedisItem : this.fieldCacheList) {
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, fieldRedisItem.getKeyPrefix());
            this.redisUtil.del(this.getRedisKeys(fieldRedisItem, Collections.singletonList(entity)));
        }

        if (this.selectAllDataCache != null) {
            String keyPrefix = this.selectAllDataCache.getKeyPrefix();
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, keyPrefix);
            this.redisUtil.del(keyPrefix);
        }

        return insertRecordCount;
    }


    /**
     * 批量插入实体（缓存）
     * 非主键缓存删除策略
     *
     * @param entityList 待插入的实体集合
     * @return 受影响行数
     */
    @Override
    public int insertBatchCache(List<E> entityList) {
        int insertRecordCount = entityList.stream().mapToInt(entity -> super.baseMapper.insert(entity)).sum();

        if (insertRecordCount == 0) {
            log.error("插入「{}」失败, list-> ", this.name);
            entityList.forEach(entity -> log.error(entity.toString()));
        } else if (insertRecordCount < entityList.size()) {
            log.error("没有全部插入成功「{}」, list-> ", this.name);
            entityList.forEach(entity -> log.error(entity.toString()));
        }

        for (FieldRedisItem<E> fieldRedisItem : this.fieldCacheList) {
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, fieldRedisItem.getKeyPrefix());
            this.redisUtil.del(this.getRedisKeys(fieldRedisItem, entityList));
        }

        if (this.selectAllDataCache != null) {
            String keyPrefix = this.selectAllDataCache.getKeyPrefix();
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, keyPrefix);
            this.redisUtil.del(keyPrefix);
        }

        return insertRecordCount;
    }


    /**
     * 更新实体（缓存）
     * 全缓存删除策略
     *
     * @param entity 待更新的实体
     * @return 受影响行数
     */
    @Override
    public int updateCache(E entity) {
        int updateRecordCount = super.baseMapper.updateById(entity);

        if (updateRecordCount == 0) {
            log.error("更新「{}」失败", this.name);
            log.error(entity.toString());
            return updateRecordCount;
        }

        for (FieldRedisItem<E> fieldRedisItem : this.allFieldCacheList) {
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, fieldRedisItem.getKeyPrefix());
            this.redisUtil.del(this.getRedisKeys(fieldRedisItem, Collections.singletonList(entity)));
        }

        return updateRecordCount;
    }


    /**
     * 批量更新实体（缓存）
     * 全缓存删除策略
     *
     * @param entityList 待更新的实体集合
     * @return 受影响行数
     */
    @Override
    public int updateBatchCache(List<E> entityList) {
        int updateRecordCount = entityList.stream().mapToInt(entity -> super.baseMapper.updateById(entity)).sum();

        if (updateRecordCount == 0) {
            log.error("更新「{}」失败, list-> ", this.name);
            entityList.forEach(entity -> log.error(entity.toString()));
        } else if (updateRecordCount < entityList.size()) {
            log.error("没有全部更新成功「{}」, list-> ", this.name);
            entityList.forEach(entity -> log.error(entity.toString()));
        }

        for (FieldRedisItem<E> fieldRedisItem : this.allFieldCacheList) {
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, fieldRedisItem.getKeyPrefix());
            this.redisUtil.del(this.getRedisKeys(fieldRedisItem, entityList));
        }

        return updateRecordCount;
    }


    /**
     * 根据ids删除实体（缓存）
     * 主键缓存删除策略
     *
     * @param ids 实体ids
     * @return 受影响行数
     */
    @Override
    public int deleteByIdsCache(String... ids) {
        int deleteRecordCount = ids.length == 1 ? this.deleteOneEntity(ids[0]) : this.deleteBatchEntity(ids);
        if (deleteRecordCount == 0) return deleteRecordCount;

        log.info(DELETE_CACHE_MESSAGE_TEMPLATE, primaryKeyCacheItem.getKeyPrefix());
        this.redisUtil.del(Arrays.stream(ids).map(id ->
                primaryKeyCacheItem.getKeyPrefix() + CommonConstant.DOUBLE_COLON + id
        ).distinct().toArray(String[]::new));

        return deleteRecordCount;
    }


    /**
     * 根据ids删除实体（缓存）
     * 全缓存删除策略
     *
     * @param ids 实体ids
     * @return 受影响行数
     */
    @Override
    public int deleteByIdsAllCache(Serializable... ids) {
        List<E> entityList = this.selectListByIdsCache(ids);
        if (entityList.isEmpty()) return 0;

        return deleteAllCache(entityList, ids);
    }


    /**
     * 根据实体集合删除实体（缓存）
     * 全缓存删除策略
     *
     * @param entityList 从这里获取RedisKey的实体集合
     * @return 受影响行数
     */
    @Override
    public int deleteByEntityListAllCache(Collection<E> entityList) {
        if (entityList.isEmpty()) return 0;
        Serializable[] ids = entityList.stream().map(entity ->
                this.primaryKeyCacheItem.getFieldFunction().apply(entity)).toArray(Serializable[]::new);

        return deleteAllCache(entityList, ids);
    }


    /**
     * 根据ids删除数据库数据
     * 根据entityList删除设置的所有缓存
     *
     * @param entityList 从这删除所有RedisKey的实体集合
     * @param ids        从这里从数据库删除数据
     * @return 受影响行数
     */
    private int deleteAllCache(Collection<E> entityList, Serializable... ids) {
        int deleteRecordCount = ids.length == 1 ? this.deleteOneEntity(ids[0]) : this.deleteBatchEntity(ids);
        if (deleteRecordCount == 0) return deleteRecordCount;

        for (FieldRedisItem<E> fieldRedisItem : this.allFieldCacheList) {
            log.info(DELETE_CACHE_MESSAGE_TEMPLATE, fieldRedisItem.getKeyPrefix());
            this.redisUtil.del(this.getRedisKeys(fieldRedisItem, entityList));
        }

        return deleteRecordCount;
    }


    /**
     * 单删除实体信息（缓存）
     */
    private int deleteOneEntity(Serializable id) {
        int deleteRecordCount = super.baseMapper.deleteById(id);
        if (deleteRecordCount == 0) {
            log.error("单删除「{}」失败, id-> " + id, this.name);
        }
        return deleteRecordCount;
    }


    /**
     * 批量删除实体信息（缓存）
     */
    private int deleteBatchEntity(Serializable... ids) {
        ArrayList<Serializable> idList = new ArrayList<>(ids.length);
        Collections.addAll(idList, ids);
        return this.deleteBatchEntity(idList);
    }


    private int deleteBatchEntity(List<Serializable> idList) {
        int deleteRecordCount = super.baseMapper.deleteBatchIds(idList);
        if (deleteRecordCount == 0) {
            log.error("批量删除「{}」失败, ids-> " + idList, this.name);
        } else if (deleteRecordCount < idList.size()) {
            log.error("没有全部成功批量删除「{}」, ids-> " + idList, this.name);
        }
        return deleteRecordCount;
    }


    /**
     * 从 entityList 获取 指定 key前缀的 的 Redis Keys（去重）
     *
     * @param fieldRedisItem 字段Function 与 RedisKey前缀载体
     * @param entityList     实体集合
     * @return Redis Keys（去重）
     */
    protected String[] getRedisKeys(FieldRedisItem<E> fieldRedisItem, Collection<E> entityList) {
        if (fieldRedisItem.isSelectAllData()) return new String[]{fieldRedisItem.getKeyPrefix()};

        return entityList.stream().map(entity -> {
                    // 字段值
                    String fieldValue = String.valueOf(fieldRedisItem.getFieldFunction().apply(entity));
                    if (!fieldValue.contains(StringPool.COMMA)) { // 字段值不包含「,」
                        return Collections.singletonList(fieldRedisItem.getKeyPrefix() + CommonConstant.DOUBLE_COLON + fieldValue);
                    } // 字段值包含「,」
                    return Arrays.stream(fieldValue.split(StringPool.COMMA)).map(value ->
                            fieldRedisItem.getKeyPrefix() + CommonConstant.DOUBLE_COLON + value
                    ).collect(Collectors.toList());
                })
                .flatMap(Collection::stream)
                .distinct()
                .toArray(String[]::new);
    }


    /**
     * 根据ids获取实体集合
     *
     * @param ids 商品分类id集合
     * @return 商品分类集合
     * @see #getByIdCache
     */
    @Override
    public List<E> selectListByIdsCache(Serializable... ids) {
        return Arrays.stream(ids).map(this::getByIdCache).collect(Collectors.toList());
    }


    /**
     * 检查是否存在
     *
     * @param entityList 实体集合
     * @param id         实体id
     */
    protected boolean isExist(Collection<E> entityList, Serializable id) {
        return entityList.stream().noneMatch(entity -> this.getId(entity).equals(id));
    }


    /**
     * 检查用不用更新
     * 用的时候记得把@EqualsAndHashCode的callSuper设置为 false
     *
     * @param entityList       实体集合
     * @param tobeUpdateEntity 要更新的实体
     * @return true/false
     */
    protected boolean isPossibleUpdate(Collection<E> entityList, E tobeUpdateEntity) {
        Optional<E> first = entityList.stream().filter(entity ->
                this.getId(entity).equals(this.getId(tobeUpdateEntity))).findFirst();
        if (!first.isPresent())
            throw new CheckFailedException("未找到此" + this.name);

        return first.get().equals(tobeUpdateEntity);
    }


    /**
     * 检查字段是否重复
     *
     * @param entityList      实体集合
     * @param tobeCheckEntity 待检查的实体
     */
    protected boolean isRepeatField(Collection<E> entityList, E tobeCheckEntity, Function<E, Serializable> field) {
        return entityList.stream().anyMatch(entity -> !this.getId(entity)
                .equals(this.getId(tobeCheckEntity)) && field.apply(entity).equals(field.apply(tobeCheckEntity)));
    }


}
